import gzip
import struct

    # # Helper class for manipulating the minecraft named binary tag format. The specification for this format can be found in
    # # http://www.minecraft.net/docs/NBT.txt

class FileBuffer:
    def __init__(self, path):
        self.path = path
        self.data = gzip.open(path, 'r').read()
        self.offset = 0
        self.full = len(self.data)

    def isEmpty(self):
        return self.offset == self.full

    def readByte(self):
        r = struct.unpack_from(">c", self.data, self.offset)[0]
        self.offset += struct.calcsize(">c")
        return ord(r)

    def readShort(self):
        r = struct.unpack_from(">h", self.data, self.offset)[0]
        self.offset += struct.calcsize(">h")
        return r

    def readInt(self):
        r = struct.unpack_from(">i", self.data, self.offset)[0]
        self.offset += struct.calcsize(">i")
        return r

    def readLong(self):
        r = struct.unpack_from(">q", self.data, self.offset)[0]
        self.offset += struct.calcsize(">q")
        return r

    def readFloat(self):
        r = struct.unpack_from(">f", self.data, self.offset)[0]
        self.offset += struct.calcsize(">f")
        return r

    def readDouble(self):
        r = struct.unpack_from(">d", self.data, self.offset)[0]
        self.offset += struct.calcsize(">d")
        return r

    def readByteArray(self):
        size = self.readInt()
        r = self.data[self.offset: self.offset + size]
        self.offset += size
        return r

    def readString(self):
        size = self.readShort()
        r = self.data[self.offset: self.offset + size]
        self.offset += size
        return r

class NBT:
    def __init__(self, path):
        self.fb = FileBuffer(path)

    def Read(self):
        t = self.fb.readByte()
        if t is not None:
            #print "Reading in", self.fb.path, t
            return self.readAsType(t)
        return None

    # Interprets the remaining stream as a named NBT datum with the specified type.
    def readAsType(self, type):
        return NBTNamedTag(self.fb.readString(), self.readUnamedAsType(type))

    # Interprets the remaining stream as an unamed NBT datum with the specified type.
    def readUnamedAsType(self, type):
        if type == NBTByte.type:
            return NBTByte(self.fb.readByte())
        elif type == NBTShort.type:
            return NBTShort(self.fb.readShort())
        elif type == NBTInt.type:
            return NBTInt(self.fb.readInt())
        elif type == NBTLong.type:
            return NBTLong(self.fb.readLong())
        elif type == NBTFloat.type:
            return NBTFloat(self.fb.readFloat())
        elif type == NBTDouble.type:
            return NBTDouble(self.fb.readDouble())
        elif type == NBTByteArray.type:
            return NBTByteArray(self.fb.readByteArray())
        elif type == NBTString.type:
            return NBTString(self.fb.readString())
        elif type == NBTList.type:
            subtype = self.fb.readByte()
            length = self.fb.readInt()
            data = []
            for i in range(length):
                data.append(self.readUnamedAsType(subtype))
            return NBTList(subtype, data)
        elif type == NBTCompound.type:
            data = {}
            while True:
                subtype = self.fb.readByte()
                if subtype != 0:
                    item = self.readAsType(subtype)
                    data[item.name] = item
                else:
                    break
            return NBTCompound(data)
        else:
            print "Unrecognized type!", type, type.__class__

# Data that can be stored in the NBT format.
class INBTData:
    def __init__(self):
        pass

# Signed byte.
class NBTByte(INBTData):
    type = 1;
    def __init__(self, data):
        self.data = data;

# Signed short.
class NBTShort(INBTData):
    type = 2;
    def __init__(self, data):
        self.data = data;

# Signed int.
class NBTInt(INBTData):
    type = 3;
    def __init__(self, data):
        self.data = data;

# Signed long.
class NBTLong(INBTData):
    type = 4;
    def __init__(self, data):
        self.data = data;

# 32 bit float.
class NBTFloat(INBTData):
    type = 5;
    def __init__(self, data):
        self.data = data;

# 64 bit float.
class NBTDouble(INBTData):
    type = 6;
    def __init__(self, data):
        self.data = data;

# Byte array.
class NBTByteArray(INBTData):
    type = 7;
    def __init__(self, data):
        self.data = data # byte array

# UTF-8 string.
class NBTString(INBTData):
    type = 8;
    def __init__(self, data):
        self.data = data # str

# Homogenous list of unamed data.
class NBTList(INBTData):
    type = 9;
    def __init__(self, subType, data):
        self.subType = subType
        self.data = data # list

# Heterogenous set of named data.
class NBTCompound(INBTData):
    type = 10;
    def __init__(self, data):
        self.data = data # dict

# Data wrapped in a named tag with the NBT format.
class NBTNamedTag(INBTData):
    def __init__(self, name, data):
        #print "Making NBTNamedTag ", name
        self.name = name
        self.data = data
